package truerefactor.graph;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * CodeGraph - A DiGraph representation of a code base. It not only contains the
 * code within the code base, but also represents a nearly identical to UML
 * class diagram graph of the entire code base as well. This data structure allows the 
 * ability to easily modify the code and generate the necessary information about the
 * code structure without having to do to heavy of a translation.
 * 
 * @author Isaac
 */
public class CodeGraph extends DiGraph {

    /**
     * Static counter for the next unique id number
     */
    private static long nextUID = 0;

    /**
     * List of all packages in the graph
     */
    private List<PackageNode> packages;
    /**
     * List of all classes in the graph
     */
    private List<ClassNode> classes;

    /**
     * Constructs a new Empty CodeGraph
     */
    public CodeGraph()
    {
        adjacencies = new HashMap<CodeNode, List<Edge>>();
        sources = new LinkedList<CodeNode>();
        sinks = new LinkedList<CodeNode>();
        nodes = new CopyOnWriteArrayList<CodeNode>();
        classes = new LinkedList<ClassNode>();
        packages = new LinkedList<PackageNode>();
    }

    // public CodeGraph(GraphType type) {
    // super();
    // this.type = type;
    // }

    /*
     * (non-Javadoc)
     * @see loopmanager.DiGraph#NumberOfNodes()
     */
    @Override
    public synchronized int numberOfNodes()
    {
        return nodes.size();
    }

    /*
     * (non-Javadoc)
     * @see loopmanager.DiGraph#getEdge(loopmanager.CodeNode,
     * loopmanager.CodeNode)
     */
    @Override
    public synchronized Edge getEdge(CodeNode source, CodeNode destination, EdgeType type)
    {
        Edge retVal = null;

        if (adjacencies.containsKey(source))
        {
            List<Edge> temp = adjacencies.get(source);

            for (Edge e : temp)
            {
                if (e.getDestination().equals(destination))
                {
                    if (e.getType().equals(type))
                    {
                        retVal = e;
                        break;
                    }
                }
            }
        }

        return retVal;
    }

    /*
     * (non-Javadoc)
     * @see loopmanager.DiGraph#getIterator()
     */
    @Override
    public synchronized GraphIterator getIterator()
    {
        return new GraphIterator(this);
    }

    /*
     * (non-Javadoc)
     * @see loopmanager.DiGraph#insert(loopmanager.DependencyEdge)
     */
    /**
     * FIXME requires substantial refactoring
     */
    @Override
    public synchronized void insert(Edge edge)
    {
        CodeNode source = edge.getSource();
        CodeNode destination = edge.getDestination();

        if (source == null || destination == null)
            return;

        if (adjacencies.containsKey(source))
        {
            List<Edge> temp = adjacencies.get(source);
            if (!temp.contains(edge))
            {
                temp.add(edge);
            }

            if (!adjacencies.containsKey(destination))
            {
                adjacencies.put(destination, new CopyOnWriteArrayList<Edge>());
            }
        }
        else
        {
            CopyOnWriteArrayList<Edge> temp = new CopyOnWriteArrayList<Edge>();
            temp.add(edge);
            adjacencies.put(source, temp);

            if (!adjacencies.containsKey(destination))
            {
                adjacencies.put(destination, new CopyOnWriteArrayList<Edge>());
            }
        }

        if (sources.contains(destination))
        {
            sources.remove(destination);
        }

        if (sinks.contains(destination) && !adjacencies.get(destination).isEmpty())
        {
            sinks.remove(destination);
        }

        if (sinks.contains(source))
        {
            sinks.remove(source);
        }

        if (source instanceof ClassNode)
        {
            if (destination instanceof MethodNode)
            {
                ((MethodNode) destination).setParentClass((ClassNode) source);
                ((ClassNode) source).addMethod((MethodNode) destination);
            }
            else if (destination instanceof AttributeNode)
            {
                ((AttributeNode) destination).setParent((ClassNode) source);
                ((ClassNode) source).addAttribute((AttributeNode) destination);
            }
            else if (destination instanceof ClassNode)
            {
                switch (edge.getType())
                {
                case Generalization:
                case InterfaceRealization:
                    ClassNode parent = (ClassNode) source;
                    ClassNode subclass = (ClassNode) destination;
                    subclass.addParentClass(parent);
                    parent.addSubClass(subclass);
                    break;
                }
            }
        }
        else if (source instanceof StatementNode)
        {
            if (destination instanceof StatementNode)
            {
                ((StatementNode) destination).setParent(((StatementNode) source).getParent());
            }
        }
        else if (source instanceof MethodNode)
        {
            if (destination instanceof StatementNode)
            {
                ((StatementNode) destination).setParent((MethodNode) source);
            }
            else if (destination instanceof AttributeNode)
            {
                if (!((AttributeNode) destination).getParent().equals(((MethodNode) source).getParentClass()))
                {
                    ClassNode to = ((AttributeNode) destination).getParent();
                    ClassNode from = ((MethodNode) source).getParentClass();
                    insert(new Edge(to, from, EdgeType.UseDependency));
                    to.addAssociatedClass(from);
                }
            }
            else if (destination instanceof MethodNode)
            {
                if (!((MethodNode) destination).getParentClass().equals(((MethodNode) source).getParentClass()))
                {
                    ClassNode to = ((MethodNode) destination).getParentClass();
                    ClassNode from = ((MethodNode) source).getParentClass();
                    insert(new Edge(to, from, EdgeType.UseDependency));
                    to.addAssociatedClass(from);
                }
            }
        }
        else if (source instanceof AttributeNode)
        {
            AttributeNode aNode = (AttributeNode) source;
            if (destination instanceof ClassNode)
            {
                ClassNode cNode = (ClassNode) destination;
                ClassNode aParent = aNode.getParent();

                if (aParent != null && cNode != null)
                {
                    if (!aParent.equals(cNode))
                    {
                        aParent.addAssociatedClass(cNode);
                        insert(new Edge(aParent, cNode, EdgeType.UseDependency));
                    }
                }
            }
            else if (destination instanceof MethodNode)
            {
                MethodNode mNode = (MethodNode) destination;
                ClassNode cNode = mNode.getParentClass();
                ClassNode aParent = aNode.getParent();

                if (aParent != null && cNode != null)
                {
                    if (!aParent.equals(cNode))
                    {
                        cNode.addAssociatedClass(aParent);
                    }
                }
            }
        }
        else if (source instanceof PackageNode)
        {
            if (destination instanceof PackageNode)
            {
                PackageNode pkgSource = (PackageNode) source;
                PackageNode pkgDest = (PackageNode) destination;

                pkgSource.addSubPackage(pkgDest);
                pkgDest.setSuperPackage(pkgSource);
            }
            else if (destination instanceof ClassNode)
            {
                PackageNode pkgSource = (PackageNode) source;
                ClassNode clsDest = (ClassNode) destination;

                clsDest.setPackage(pkgSource);
                pkgSource.add(clsDest);
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * org.neosimulation.neo.framework.graph.DiGraph#insert(org.neosimulation
     * .neo.framework.graph.CodeNode)
     */
    @Override
    public synchronized void insert(CodeNode node)
    {
        if (node != null)
        {
            for (CodeNode cnode : nodes)
            {
                if (cnode.getIdentifier().equals(node.getIdentifier()) && cnode.getCode().equals(node.getCode()))
                    return;
            }
            if (!adjacencies.containsKey(node))
            {
                adjacencies.put(node, new CopyOnWriteArrayList<Edge>());
                nodes.add(node);
                sources.add(node);
                sinks.add(node);
            }

            if (node instanceof ClassNode)
            {
                classes.add((ClassNode) node);
            }
            else if (node instanceof PackageNode)
            {
                if (!packages.contains((PackageNode) node))
                {
                    packages.add((PackageNode) node);
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see loopmanager.DiGraph#numberOfEdges()
     */
    @Override
    public synchronized int numberOfEdges()
    {
        int edgeCount = 0;

        for (CodeNode n : nodes)
        {
            if (adjacencies.containsKey(n))
                edgeCount += adjacencies.get(n).size();
        }

        return edgeCount;
    }

    /*
     * (non-Javadoc)
     * @see loopmanager.DiGraph#remove(loopmanager.CodeNode)
     */
    @Override
    /**
     * FIXME Require substantial refactoring
     */
    public synchronized void remove(CodeNode node)
    {
        if (node instanceof PackageNode)
        {
            PackageNode pkgnode = (PackageNode) node;

            ListIterator<ClassNode> pkgClasses = pkgnode.getClasses().listIterator();
            while (pkgClasses.hasNext())
            {
                ClassNode next = pkgClasses.next();
                pkgClasses.remove();

                remove(next);
            }

            ListIterator<PackageNode> subPackages = pkgnode.getSubPackages().listIterator();
            while (subPackages.hasNext())
            {
                PackageNode pNode = subPackages.next();
                subPackages.remove();

                remove(pNode);
            }

            pkgnode.getSuperPackage().removeSubPackage(pkgnode);
            packages.remove(pkgnode);
            adjacencies.remove(pkgnode);
            sources.remove(pkgnode);
            sinks.remove(pkgnode);
            nodes.remove(pkgnode);

        }
        else if (node instanceof ClassNode)
        {
            ClassNode clsnode = (ClassNode) node;

            ListIterator<ClassNode> parentClasses = clsnode.getParentClasses().listIterator();
            ListIterator<ClassNode> subClasses = clsnode.getSubClasses().listIterator();

            while (subClasses.hasNext())
            {
                ClassNode subClass = subClasses.next();
                subClasses.remove();

                remove(subClass);
            }

            while (parentClasses.hasNext())
            {
                ClassNode parent = parentClasses.next();
                parent.removeSubClass(clsnode);
            }

            for (ClassNode assoc : clsnode.getAssociatedClasses())
            {
                if (assoc != null)
                {
                    if (isEdge(clsnode, assoc, EdgeType.Association))
                    {
                        remove(getEdge(clsnode, assoc, EdgeType.Association));
                    }
                }
            }

            for (ClassNode cls : classes)
            {
                if (cls != null)
                {
                    if (cls.getAssociatedClasses().contains(clsnode))
                    {
                        cls.removeAssociatedClass(cls);
                    }
                }
            }

            ListIterator<CodeNode> methods = clsnode.getMethods().listIterator();
            while (methods.hasNext())
            {
                MethodNode method = (MethodNode) methods.next();
                methods.remove();

                remove(method);
            }

            ListIterator<CodeNode> attributes = clsnode.getAttributes().listIterator();
            while (attributes.hasNext())
            {
                AttributeNode attr = (AttributeNode) attributes.next();
                attributes.remove();

                remove(attr);
            }

            List<AttributeNode> toRemove = new LinkedList<AttributeNode>();

            for (ClassNode cNode : classes)
            {
                for (CodeNode aNode : cNode.getAttributes())
                {
                    if (aNode != null)
                    {
                        AttributeNode attr = (AttributeNode) aNode;
                        ClassNode temp = attr.getAssociatedClass();
                        String type = attr.getType();
                        if (type.equals(cNode.getIdentifier()))
                        {
                            toRemove.add(attr);
                        }
                        else if (temp != null && temp.equals(cNode))
                        {
                            toRemove.add(attr);
                        }
                    }
                }

                if (isEdge(clsnode, cNode, EdgeType.UseDependency))
                {
                    remove(getEdge(clsnode, cNode, EdgeType.UseDependency));
                }
                if (isEdge(cNode, clsnode, EdgeType.UseDependency))
                {
                    remove(getEdge(cNode, clsnode, EdgeType.UseDependency));
                }
                if (isEdge(clsnode, cNode, EdgeType.Association))
                {
                    remove(getEdge(clsnode, cNode, EdgeType.Association));
                }
            }

            for (AttributeNode aNode : toRemove)
            {
                aNode.setType("");
                aNode.setAssociatedClass(null);
            }

            adjacencies.remove(clsnode);
            classes.remove(clsnode);
            sources.remove(clsnode);
            sinks.remove(clsnode);
            nodes.remove(clsnode);
        }
        else if (node instanceof MethodNode)
        {
            MethodNode method = (MethodNode) node;

            for (CodeNode cNode : nodes)
            {
                if (cNode instanceof ClassNode)
                {
                    ClassNode cls = (ClassNode) cNode;

                    if (cls.getMethods().contains(method))
                    {
                        cls.removeMethod(method);

                        if (isEdge(cls, method, EdgeType.Method))
                            remove(getEdge(cls, method, EdgeType.Method));
                        if (isEdge(method, cls, EdgeType.Method))
                            remove(getEdge(cls, method, EdgeType.Method));
                    }
                }
                else if (cNode instanceof MethodNode)
                {
                    MethodNode mNode = (MethodNode) cNode;

                    if (mNode.getAssociatedMethods().contains(method))
                    {
                        mNode.removeAssociatedMethod(method);

                        if (isEdge(mNode, method, EdgeType.IntraClassAssociation))
                            remove(getEdge(mNode, method, EdgeType.IntraClassAssociation));
                        if (isEdge(method, mNode, EdgeType.IntraClassAssociation))
                            remove(getEdge(method, mNode, EdgeType.IntraClassAssociation));
                    }
                    else if (mNode.getOtherMethods().contains(method))
                    {
                        mNode.removeOtherMethod(method);

                        if (isEdge(method.getParentClass(), mNode.getParentClass(), EdgeType.UseDependency))
                            remove(getEdge(method.getParentClass(), mNode.getParentClass(), EdgeType.UseDependency));
                    }

                    adjacencies.remove(method);
                    sources.remove(method);
                    sinks.remove(method);
                    nodes.remove(method);
                }
                else if (cNode instanceof AttributeNode)
                {
                    AttributeNode attr = (AttributeNode) cNode;

                    if (attr.getAssociatedMethods().contains(method))
                    {
                        attr.removeAssociatedClassMethod(method);

                        if (isEdge(method, attr, EdgeType.IntraClassAssociation))
                            remove(getEdge(method, attr, EdgeType.IntraClassAssociation));
                        if (isEdge(attr, method, EdgeType.IntraClassAssociation))
                            remove(getEdge(method, attr, EdgeType.IntraClassAssociation));

                    }
                    else if (attr.getOtherMethods().contains(method))
                    {
                        attr.removeAssociatedOtherMethod(method);
                        if (isEdge(method.getParentClass(), attr.getParent(), EdgeType.UseDependency))
                            remove(getEdge(method.getParentClass(), attr.getParent(), EdgeType.UseDependency));
                    }

                    if (method.getAssocAttrs().contains(attr))
                    {
                        if (isEdge(method, attr, EdgeType.IntraClassAssociation))
                            remove(getEdge(method, attr, EdgeType.IntraClassAssociation));
                        if (isEdge(attr, method, EdgeType.IntraClassAssociation))
                            remove(getEdge(method, attr, EdgeType.IntraClassAssociation));
                    }
                    else if (method.getOtherAttributes().contains(attr))
                    {
                        if (isEdge(method.getParentClass(), attr.getParent(), EdgeType.UseDependency))
                            remove(getEdge(method.getParentClass(), attr.getParent(), EdgeType.UseDependency));
                    }
                }
            }

        }
        else if (node instanceof AttributeNode)
        {
            AttributeNode attr = (AttributeNode) node;

            for (CodeNode cNode : nodes)
            {
                if (cNode instanceof MethodNode)
                {
                    MethodNode method = (MethodNode) cNode;
                    if (method.getAssocAttrs().contains(attr))
                    {
                        method.removeAssociatedAttribute(attr);
                        if (isEdge(method, attr, EdgeType.IntraClassAssociation))
                        {
                            remove(getEdge(method, attr, EdgeType.IntraClassAssociation));
                        }
                        if (isEdge(attr, method, EdgeType.IntraClassAssociation))
                        {
                            remove(getEdge(attr, method, EdgeType.IntraClassAssociation));
                        }
                    }
                    else if (method.getOtherAttributes().contains(attr))
                    {
                        method.removeOtherAttribute(attr);
                        if (isEdge(attr.getParent(), method.getParentClass(), EdgeType.UseDependency))
                        {
                            remove(getEdge(attr.getParent(), method.getParentClass(), EdgeType.UseDependency));
                        }
                    }
                }
                else if (cNode instanceof ClassNode)
                {
                    ClassNode cls = (ClassNode) cNode;
                    if (cls.getAttributes().contains(attr))
                    {
                        cls.removeAttribute(attr);
                    }

                    if (isEdge(attr, cls, EdgeType.Attribute))
                        remove(getEdge(attr, cls, EdgeType.Attribute));
                    if (isEdge(cls, attr, EdgeType.Attribute))
                        remove(getEdge(cls, attr, EdgeType.Attribute));
                }

                adjacencies.remove(attr);
                sinks.remove(attr);
                sources.remove(attr);
                nodes.remove(attr);
            }
        }
        else if (node instanceof StatementNode)
        {
            StatementNode sNode = (StatementNode) node;

            MethodNode parent = sNode.getParent();
            parent.getStatements().remove(sNode);

            adjacencies.remove(sNode);
            nodes.remove(sNode);
            sources.remove(sNode);
            sinks.remove(sNode);
        }
    }

    /*
     * (non-Javadoc)
     * @see loopmanager.DiGraph#remove(loopmanager.DependencyEdge)
     */
    @Override
    public synchronized void remove(Edge edge)
    {
        CodeNode source = edge.getSource();

        if (adjacencies.containsKey(source))
        {
            List<Edge> temp = adjacencies.get(source);

            if (temp.contains(edge))
            {
                temp.remove(edge);
                adjacencies.put(source, temp);
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see org.neosimulation.neo.framework.graph.DiGraph#get(int)
     */
    @Override
    public synchronized CodeNode get(int index)
    {
        if (nodes == null)
        {
            nodes = new CopyOnWriteArrayList<CodeNode>();

            for (CodeNode n : adjacencies.keySet())
            {
                nodes.add(n);
            }
        }

        if (index >= 0 && (nodes.size() - 1) >= index)
        {
            return nodes.get(index);
        }
        else
        {
            return null;
        }
    }

    /**
     * @param node
     * @return
     */
    public synchronized CodeNode get(CodeNode node)
    {
        CodeNode retVal = null;
        if (nodes != null && node != null)
        {
            for (CodeNode node2 : nodes)
            {
                if (node2.getUID() == node.getUID())
                {
                    retVal = node2;
                    break;
                }
            }
        }

        return retVal;
    }

    /*
     * (non-Javadoc)
     * @see org.neosimulation.neo.framework.graph.DiGraph#getAdjacencies(org.
     * neosimulation.neo.framework.graph.CodeNode)
     */
    @Override
    public synchronized CopyOnWriteArrayList<CodeNode> getAdjacencies(CodeNode node)
    {
        CopyOnWriteArrayList<CodeNode> adjList = new CopyOnWriteArrayList<CodeNode>();

        if (node != null && adjacencies.containsKey(node))
        {
            for (Edge p : adjacencies.get(node))
            {
                adjList.add(p.getDestination());
            }
        }
        return adjList;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.neosimulation.neo.framework.graph.DiGraph#getIndex(org.neosimulation
     * .neo.framework.graph.CodeNode)
     */
    @Override
    public synchronized int indexOf(CodeNode node)
    {
        if (nodes == null)
        {
            nodes = new CopyOnWriteArrayList<CodeNode>();

            for (CodeNode n : adjacencies.keySet())
            {
                nodes.add(n);
            }
        }

        if (nodes.contains(node))
        {
            return nodes.indexOf(node);
        }
        else
        {
            return -1;
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * org.neosimulation.neo.framework.graph.DiGraph#isEdge(org.neosimulation
     * .neo.framework.graph.CodeNode,
     * org.neosimulation.neo.framework.graph.CodeNode)
     */
    @Override
    public synchronized boolean isEdge(CodeNode source, CodeNode destination, EdgeType type)
    {
        boolean retVal = false;

        if (adjacencies.containsKey(source))
        {
            List<Edge> edges = adjacencies.get(source);
            for (Edge edge : edges)
            {
                if (edge.getDestination().equals(destination))
                {
                    if (edge.getType().equals(type))
                    {
                        retVal = true;
                        break;
                    }
                }
            }
        }

        return retVal;
    }

    /*
     * (non-Javadoc)
     * @see org.neosimulation.neo.framework.graph.DiGraph#getSinks()
     */
    @Override
    public synchronized List<CodeNode> getSinks()
    {
        return sinks;
    }

    /*
     * (non-Javadoc)
     * @see org.neosimulation.neo.framework.graph.DiGraph#getSources()
     */
    @Override
    public synchronized List<CodeNode> getSources()
    {
        return sources;
    }

    /*
     * (non-Javadoc)
     * @see truerefactor.graph.DiGraph#diff(truerefactor.graph.DiGraph)
     */
    public synchronized Set<CodeNode> diff(DiGraph other)
    {
        Set<CodeNode> diffNodes = new HashSet<CodeNode>();

        List<CodeNode> nodes = new LinkedList<CodeNode>();
        for (int i = 0; i < other.numberOfNodes(); i++)
        {
            nodes.add(other.get(i));
        }

        for (int i = 0; i < numberOfNodes(); i++)
        {
            if (nodes.contains(get(i)))
            {
                CodeNode node = nodes.get(nodes.indexOf(get(i)));
                if (other.getAdjacencies(node).size() != getAdjacencies(get(i)).size())
                {
                    diffNodes.add(get(i));
                }
                else
                {
                    nodes.remove(node);
                }
            }
        }

        if (!nodes.isEmpty())
        {
            diffNodes.addAll(nodes);
        }

        return diffNodes;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj)
    {
        if (obj instanceof CodeGraph)
        {
            CodeGraph depGraph = (CodeGraph) obj;

            if (depGraph.diff(this).isEmpty())
            {
                return true;
            }
        }

        return false;
    }

    /**
     * @param cNode
     *            parent class node of method to find
     * @param methodName
     *            name of method to find
     * @param params
     *            parameter information for method to find
     * @return Method that meets the search criteria of the other parameters
     */
    public MethodNode getMethod(ClassNode cNode, String methodName, List<TypePair> params)
    {
        List<CodeNode> methods = cNode.getMethods();
        MethodNode retVal = null;

        for (CodeNode node : methods)
        {
            if (node instanceof MethodNode)
            {
                MethodNode mNode = (MethodNode) node;
                if (mNode.getIdentifier().equals(methodName) && mNode.getAllParamInfo().equals(params))
                {
                    retVal = mNode;
                }
            }
        }

        return retVal;
    }

    /**
     * @return list of all ClassNodes in this graph
     */
    public List<ClassNode> getClasses()
    {
        return classes;
    }

    /**
     * @return List of all MethodNodes in this graph
     */
    public List<MethodNode> getMethods()
    {
        List<MethodNode> methods = new LinkedList<MethodNode>();
        for (ClassNode cNode : classes)
        {
            for (CodeNode node : cNode.getMethods())
            {
                if (node instanceof MethodNode)
                {
                    methods.add((MethodNode) node);
                }
            }
        }

        return methods;
    }

    /**
     * @return list of all AttributeNodes in this graph
     */
    public List<AttributeNode> getAttributes()
    {
        List<AttributeNode> attrs = new LinkedList<AttributeNode>();
        for (ClassNode cNode : classes)
        {
            for (CodeNode node : cNode.getAttributes())
            {
                if (node instanceof AttributeNode)
                {
                    attrs.add((AttributeNode) node);
                }
            }
        }

        return attrs;
    }

    /**
     * @param newNode
     * @param oldNode
     * @param graph
     */
    private void fixClassNode(ClassNode newNode, ClassNode oldNode, CodeGraph graph)
    {
        for (CodeNode aNode : oldNode.getAttributes())
        {
            newNode.addAttribute((AttributeNode) graph.get(aNode));
        }

        for (CodeNode mNode : oldNode.getMethods())
        {
            newNode.addMethod((MethodNode) graph.get(mNode));
        }

        for (String ext : oldNode.getExtends())
        {
            newNode.addExtends(new String(ext));
        }

        for (String impl : oldNode.getImplements())
        {
            newNode.addImplements(new String(impl));
        }

        newNode.setPackage(graph.getPackage(oldNode.getPackage().getIdentifier()));

        for (ClassNode cNode : oldNode.getParentClasses())
        {
            newNode.addParentClass((ClassNode) graph.get(cNode));
        }

        for (ClassNode cNode : oldNode.getSubClasses())
        {
            newNode.addSubClass((ClassNode) graph.get(cNode));
        }
    }

    /*
     * (non-Javadoc)
     * @see truerefactor.graph.DiGraph#getNodes()
     */
    public List<CodeNode> getNodes()
    {
        return this.nodes;
    }

    /**
     * @return a list of all known packages within this graph
     */
    public List<PackageNode> getPackages()
    {
        return packages;
    }

    /**
     * Searches this graph for a Package with the same fully qualified name as
     * the provided one.
     * 
     * @param name
     *            fully qualified name of the package being searchee for
     * @return Package with same fully qualified name as the provided name, or
     *         null if no such package exists in this graph
     */
    public PackageNode getPackage(String name)
    {
        PackageNode retVal = null;

        for (PackageNode pNode : packages)
        {
            if (pNode.getIdentifier().equals(name))
                retVal = pNode;
        }

        return retVal;
    }

    /**
     * @return list of all root packages, that is packages that are not
     *         sub-packages of other packages
     */
    public List<PackageNode> getRootPackages()
    {
        List<PackageNode> retVals = new LinkedList<PackageNode>();

        for (PackageNode pNode : packages)
        {
            if (pNode.getSuperPackage() == null)
            {
                retVals.add(pNode);
            }
        }

        return retVals;
    }

    /**
     * connects all classes together based on class hierarchical relationships
     */
    public void connectClasses()
    {
        for (ClassNode cNode : classes)
        {
            List<String> impls = cNode.getImplements();
            List<String> extds = cNode.getExtends();

            for (String impl : impls)
            {
                if (cNode != null && getClass(impl) != null)
                {
                    insert(new Edge(getClass(impl), cNode, EdgeType.InterfaceRealization));
                }
            }

            for (String extd : extds)
            {
                if (cNode != null && getClass(extd) != null)
                {
                    insert(new Edge(getClass(extd), cNode, EdgeType.Generalization));
                }
            }

            for (ClassNode assoc : cNode.getAssociatedClasses())
            {
                if (assoc != null)
                {
                    insert(new Edge(cNode, assoc, EdgeType.Association));
                }
            }

            cNode.getPackage().add(cNode);
        }
    }

    /**
     * connects all methods to their parent classes, and connects classes based
     * on method references to other classes.
     */
    public void connectMethods()
    {
        List<CodeNode> methods = new LinkedList<CodeNode>();

        for (CodeNode node : nodes)
        {
            if (node instanceof MethodNode)
                methods.add(node);
        }

        for (CodeNode node : methods)
        {
            if (node instanceof MethodNode)
            {
                MethodNode method = (MethodNode) node;
                insert(new Edge(method.getParentClass(), method, EdgeType.Method));

                for (AttributeNode aNode : method.getOtherAttributes())
                {
                    ClassNode toClass = aNode.getParent();
                    ClassNode fromClass = method.getParentClass();

                    insert(new Edge(fromClass, toClass, EdgeType.UseDependency));
                }

                for (AttributeNode aNode : method.getAssocAttrs())
                {
                    insert(new Edge(aNode, method, EdgeType.IntraClassAssociation));
                    insert(new Edge(method, aNode, EdgeType.IntraClassAssociation));
                }

                for (MethodNode mNode : method.getOtherMethods())
                {
                    ClassNode toClass = mNode.getParentClass();
                    ClassNode fromClass = method.getParentClass();

                    insert(new Edge(fromClass, toClass, EdgeType.UseDependency));
                }

                for (MethodNode mNode : method.getAssociatedMethods())
                {
                    insert(new Edge(mNode, method, EdgeType.IntraClassAssociation));
                    insert(new Edge(method, mNode, EdgeType.IntraClassAssociation));
                }
            }
        }
    }

    /**
     * connects all attributes to their parent classes and classes to other
     * classes based on attribute type and attribute other refereces
     */
    public void connectAttributes()
    {
        List<CodeNode> attrs = new LinkedList<CodeNode>();

        for (ClassNode cNode : classes)
        {
            attrs.addAll(cNode.getAttributes());
        }

        for (CodeNode node : attrs)
        {
            if (node instanceof AttributeNode)
            {
                AttributeNode attr = (AttributeNode) node;
                insert(new Edge(attr.getParent(), attr, EdgeType.Attribute));

                for (MethodNode method : attr.getOtherMethods())
                {

                    ClassNode toClass = attr.getParent();
                    ClassNode fromClass = method.getParentClass();

                    insert(new Edge(fromClass, toClass, EdgeType.UseDependency));

                    toClass = fromClass;
                    fromClass = getClass(attr.getType());

                    insert(new Edge(fromClass, toClass, EdgeType.Association));
                }

                ListIterator<MethodNode> iter = attr.getAssociatedMethods().listIterator();
                while (iter.hasNext())
                {
                    MethodNode method = iter.next();
                    if (method == null)
                    {
                        iter.remove();
                    }
                    else
                    {
                        insert(new Edge(method, attr, EdgeType.IntraClassAssociation));
                        insert(new Edge(attr, method, EdgeType.IntraClassAssociation));
                    }
                }
            }
        }
    }

    /**
     * returns a class from the graph with the same fully qualified name the
     * that which is provided
     * 
     * @param fullClassName
     *            the fully qualified class name of the class being searched for
     *            in the graph
     * @return ClassNode with the same fully qualified class name as the one
     *         provided, or null if no such class exists in this graph
     */
    public ClassNode getClass(String fullClassName)
    {
        ClassNode retVal = null;

        for (ClassNode cNode : classes)
        {
            if (cNode.getIdentifier().equals(fullClassName))
            {
                retVal = cNode;
                break;
            }
        }

        return retVal;
    }

    /**
     * connects the package hierarchy together inside the graph
     */
    public void connectPackages()
    {
        for (PackageNode pNode : packages)
        {
            String base = pNode.getBase();
            Stack<String> pkgChain = new Stack<String>();

            StringTokenizer tok = new StringTokenizer(base, ".");
            while (tok.hasMoreTokens())
            {
                pkgChain.push(tok.nextToken());
            }

            while (!pkgChain.isEmpty())
            {
                PackageNode supPkg = getPackage(pkgChain.pop());
                if (supPkg != null)
                {
                    if (!isEdge(supPkg, pNode, EdgeType.PackageDependency))
                    {
                        insert(new Edge(supPkg, pNode, EdgeType.PackageDependency));
                    }

                    pNode.setSuperPackage(supPkg);
                    supPkg.addSubPackage(pNode);
                }
            }

            for (ClassNode cNode : pNode.getClasses())
            {
                insert(new Edge(pNode, cNode, EdgeType.Package));
            }
        }
    }

    /**
     * @return the next unique id
     */
    public static long nextUID()
    {
        return nextUID++;
    }

    /**
     * Method used to clone the provided CodeGraph into the provided
     * MethodNode's statement graph
     * 
     * @param parent
     *            method into which the old graph will be cloned into
     * @param oldGraph
     *            existing statement graph to be cloned
     */
    public void cloneStatementGraph(MethodNode parent, CodeGraph oldGraph)
    {
        List<CodeNode> statements = oldGraph.getNodes();
        CodeGraph otherStmtGraph = parent.getStatementGraph();

        for (CodeNode cNode : statements)
        {
            if (cNode instanceof StatementNode)
            {
                StatementNode stmt = (StatementNode) cNode;
                StatementNode temp;
                try
                {
                    temp = (StatementNode) stmt.clone();
                    temp.setParent(parent);

                    otherStmtGraph.insert(temp);
                    parent.getStatements().add(temp);
                }
                catch (CloneNotSupportedException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

        for (CodeNode cNode : oldGraph.getNodes())
        {
            for (CodeNode adjNode : oldGraph.getAdjacencies(cNode))
            {
                if (otherStmtGraph.get(cNode) != null && otherStmtGraph.get(adjNode) != null)
                {
                    CodeNode otherCNode = otherStmtGraph.get(cNode);
                    CodeNode otherAdjNode = otherStmtGraph.get(adjNode);

                    if (otherCNode != null && otherAdjNode != null)
                        otherStmtGraph.insert(new Edge(otherCNode, otherAdjNode, EdgeType.Statement));
                }
            }
        }
    }

    /**
     * Clears this graph of all nodes and edges.
     */
    public void clear()
    {
        this.adjacencies.clear();
        this.classes.clear();
        this.nodes.clear();
        this.packages.clear();
        this.sinks.clear();
        this.sources.clear();
    }

    /**
     * FIXME requires massive refactoring. Creates a clone of this graph in the
     * provided graph
     */
    public void cloneInto(CodeGraph graph) throws CloneNotSupportedException
    {
        for (ClassNode cNode : classes)
        {
            ClassNode newNode = (ClassNode) cNode.clone();
            graph.insert(newNode);
        }

        for (PackageNode pNode : packages)
        {
            PackageNode newNode = (PackageNode) pNode.clone();
            graph.insert(newNode);
        }

        for (CodeNode node : nodes)
        {
            if (node instanceof MethodNode)
            {
                MethodNode newNode = (MethodNode) ((MethodNode) node).clone();
                ClassNode parent = (ClassNode) graph.get(((MethodNode) node).getParentClass());
                if (parent != null)
                {
                    newNode.setParentClass(parent);
                    graph.insert(newNode);
                }
            }
            else if (node instanceof AttributeNode)
            {
                AttributeNode newNode = (AttributeNode) ((AttributeNode) node).clone();
                newNode.setParent((ClassNode) graph.get(((AttributeNode) node).getParent()));
                graph.insert(newNode);
            }
            else if (node instanceof StatementNode)
            {
                StatementNode newNode = (StatementNode) ((StatementNode) node).clone();
                newNode.setParent((MethodNode) graph.get(((StatementNode) node).getParent()));
                graph.insert(newNode);
            }
        }

        for (CodeNode node : nodes)
        {
            if (node instanceof PackageNode)
            {
                PackageNode oldNode = (PackageNode) node;
                PackageNode newNode = graph.getPackage(oldNode.getIdentifier());

                for (ClassNode cNode : oldNode.getClasses())
                {
                    newNode.add((ClassNode) graph.get(cNode));
                }

                for (PackageNode pNode : oldNode.getSubPackages())
                {
                    newNode.addSubPackage(graph.getPackage(pNode.getIdentifier()));
                }

                newNode.setSuperPackage((PackageNode) graph.get(oldNode.getSuperPackage()));
            }
            else if (node instanceof ClassNode)
            {
                ClassNode oldNode = (ClassNode) node;
                ClassNode newNode = (ClassNode) graph.get(oldNode);

                for (CodeNode aNode : oldNode.getAttributes())
                {
                    newNode.addAttribute((AttributeNode) graph.get(aNode));
                }

                for (CodeNode mNode : oldNode.getMethods())
                {
                    newNode.addMethod((MethodNode) graph.get(mNode));
                }

                for (String ext : oldNode.getExtends())
                {
                    newNode.addExtends(new String(ext));
                }

                for (String impl : oldNode.getImplements())
                {
                    newNode.addImplements(new String(impl));
                }

                newNode.setPackage(graph.getPackage(oldNode.getPackage().getIdentifier()));

                for (ClassNode cNode : oldNode.getAssociatedClasses())
                {
                    newNode.addAssociatedClass((ClassNode) graph.get(cNode));
                }

                for (ClassNode cNode : oldNode.getParentClasses())
                {
                    newNode.addParentClass((ClassNode) graph.get(cNode));
                }

                for (ClassNode cNode : oldNode.getSubClasses())
                {
                    newNode.addSubClass((ClassNode) graph.get(cNode));
                }
            }
            else if (node instanceof MethodNode)
            {
                MethodNode oldNode = (MethodNode) node;
                MethodNode newNode = (MethodNode) graph.get(oldNode);
                if (newNode == null)
                {
                    newNode = (MethodNode) oldNode.clone();

                    ClassNode newParent = (ClassNode) graph.get(oldNode.getParentClass());
                    if (newParent == null)
                    {
                        newParent = (ClassNode) oldNode.getParentClass().clone();
                        graph.insert(newParent);
                        fixClassNode(newParent, oldNode.getParentClass(), graph);
                    }

                    graph.insert(newNode);
                }

                cloneStatementGraph(newNode, oldNode.getStatementGraph());

                for (MethodNode mNode : oldNode.getAssociatedMethods())
                {
                    newNode.addAssociatedMethod((MethodNode) graph.get(mNode));
                }

                for (AttributeNode aNode : oldNode.getOtherAttributes())
                {
                    newNode.addOtherAttribute((AttributeNode) graph.get(aNode));
                }

                for (AttributeNode aNode : oldNode.getAssocAttrs())
                {
                    newNode.addAssociatedAttribute((AttributeNode) graph.get(aNode));
                }

                for (MethodNode mNode : oldNode.getOtherMethods())
                {
                    newNode.addOtherMethod((MethodNode) graph.get(mNode));
                }

                newNode.setParentClass((ClassNode) graph.get(oldNode.getParentClass()));
            }
            else if (node instanceof AttributeNode)
            {
                AttributeNode oldNode = (AttributeNode) node;
                AttributeNode newNode = (AttributeNode) (graph.get(oldNode));

                newNode.setAssociatedClass((ClassNode) graph.get(oldNode.getAssociatedClass()));

                for (MethodNode mNode : oldNode.getAssociatedMethods())
                {
                    newNode.addAssociatedClassMethod((MethodNode) graph.get(mNode));
                }

                for (MethodNode mNode : oldNode.getOtherMethods())
                {
                    newNode.addAssociatedOtherMethod((MethodNode) graph.get(mNode));
                }

                newNode.setParent((ClassNode) graph.get(oldNode.getParent()));
                oldNode.getType();
            }
        }

        graph.connectClasses();
        graph.connectPackages();
        graph.connectMethods();
        graph.connectAttributes();
    }
}
